package com.educate.common.web;

import com.educate.common.utils.action.Func2;
import com.educate.common.utils.exception.AppException;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Created by yincs on 2017/3/14.
 */
public class BeanUtils {

    /**
     * 把hashMap里的属性设置进入对象中。使用了Spring BeanUtils 缓存了target的方法。减少反射次数
     *
     * @author yincs
     */
    public static <T> T copyProperties(Map<String, Object> source, T target) {
        Assert.notNull(source, "source must not be null");
        Assert.notNull(target, "target must not be null");

        for (Map.Entry<String, Object> entry : source.entrySet()) {
            PropertyDescriptor descriptor = org.springframework.beans.BeanUtils.getPropertyDescriptor(target.getClass(), entry.getKey());
            if (descriptor != null) {
                Method writeMethod = descriptor.getWriteMethod();
                if (writeMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], entry.getValue().getClass())) {
                    try {
                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                            writeMethod.setAccessible(true);
                        }
                        writeMethod.invoke(target, entry.getValue());
                    } catch (Throwable var15) {
                        throw new AppException("Could not copy property \'" + entry.getKey() + "\' from source to target", var15);
                    }
                }
            }
        }
        return target;
    }


    /**
     * 移除hashMap在clazz中不存在的属性
     *
     * @param removeKey 另外要移除的key
     * @author yincs
     */
    public static void removeKeyNoBean(HashMap<String, Object> hashMap, Class<?> clazz, String... removeKey) {
        Assert.notNull(hashMap, "hashMap must not be null");
        Assert.notNull(clazz, "Class must not be null");
        PropertyDescriptor[] propertyDescriptors = org.springframework.beans.BeanUtils.getPropertyDescriptors(clazz);
//        Field[] fields = clazz.getDeclaredFields();
        List<String> fieldNames = new ArrayList<>(propertyDescriptors.length);
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            fieldNames.add(descriptor.getName());
        }
        Set<String> keySet = hashMap.keySet();
        keySet.retainAll(fieldNames);
        if (removeKey.length > 0)
            keySet.removeAll(Arrays.asList(removeKey));
    }

    public static void copyProperties(Object source, Object target, String[] ignorePropertiesCommon, String... ignoreProperties) {
        Set<String> checkRepeat = new HashSet<>();
        if (ignorePropertiesCommon != null) {
            Collections.addAll(checkRepeat, ignorePropertiesCommon);
        }
        Collections.addAll(checkRepeat, ignoreProperties);
        copyProperties(source, target, (Func2<Boolean, Object, Object>) null, checkRepeat.toArray(new String[checkRepeat.size()]));
    }

    public static void copyPropertiesNonnull(Object source, Object target) {
        copyPropertiesNonnull(source, target, (String[]) null);
    }

    public static void copyPropertiesNonnull(Object source, Object target, String... ignoreProperties) {
        copyProperties(source, target, new Func2<Boolean, Object, Object>() {
            @Override
            public Boolean call(Object o, Object o2) {
                return o != null;
            }
        }, ignoreProperties);
    }

    public static void copyPropertiesNonEmpty(Object source, Object target) {
        copyPropertiesNonEmpty(source, target, (String[]) null);
    }

    public static  <T> T copyProperties2(Object source, T target) {
        copyProperties(source, target);
        return target;
    }

    /**
     * 过滤source上的空属性
     *
     * @param source
     * @param target
     * @param ignoreProperties
     */
    public static void copyPropertiesNonEmpty(Object source, Object target, String... ignoreProperties) {
        copyProperties(source, target, new Func2<Boolean, Object, Object>() {
            @Override
            public Boolean call(Object o, Object o2) {
                return EmptyUtils.isNotEmpty(o);
            }
        }, ignoreProperties);
    }


    /**
     * 赋值到target属性为null的字段上
     */
    public static void copyPropertiesOverrideTargetNull(Object source, Object target, String... ignoreProperties) {
        copyProperties(source, target, new Func2<Boolean, Object, Object>() {
            @Override
            public Boolean call(Object o, Object o2) {
                return o2 == null;
            }
        }, ignoreProperties);
    }

    /**
     * 复制属性
     *
     * @param filter           过滤条件
     * @param ignoreProperties "过滤的字段"
     */
    public static void copyProperties(Object source, Object target, Func2<Boolean, Object, Object> filter, String... ignoreProperties) {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);
        List ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreProperties == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }

                            Method readMethodTarget = targetPd.getReadMethod();
                            if (!Modifier.isPublic(readMethodTarget.getDeclaringClass().getModifiers())) {
                                readMethodTarget.setAccessible(true);
                            }
                            Object targetEx = readMethodTarget.invoke(target);

                            Object ex = readMethod.invoke(source);

                            if (Objects.equals(targetEx, ex)) continue;
                            if (filter != null && Boolean.TRUE != filter.call(ex, targetEx)) {
                                continue;
                            }

                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }

                            writeMethod.invoke(target, ex);
                        } catch (Throwable var15) {
                            throw new FatalBeanException("Could not copy property \'" + targetPd.getName() + "\' from source to target", var15);
                        }
                    }
                }
            }
        }
    }

    public static void copyProperties(Object source, Object target) throws BeansException {
        if (target == null) return;
        org.springframework.beans.BeanUtils.copyProperties(source, target);
    }
}
